package com.lam.common.components.email;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.URLName;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.util.ByteArrayDataSource;

import org.apache.commons.lang3.StringUtils;
import com.lam.common.components.parameter.ParameterConfigUtil;
import com.lam.common.exception.BaseException;
import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EmailUtil {

	private static final Logger logger = LoggerFactory.getLogger(EmailUtil.class);
	
	private static final int MAX_RETRY_COUNT = 5;

    public static void main(String[] args) throws IOException{
    	
        try {
        	
        	sendEmail("lamyz0417@qq.com", "lamyz0417@qq.com", "lamyz0417@qq.com", "测试邮件111", "Dear，您好，谢谢！");
        	
        	String fileName = "C:\\Users\\lam\\Desktop\\piggy.txt";
        	InputStream input = new FileInputStream(fileName);
        	
        	//附件是 输入流
        	Map<String, InputStream> attachment = new HashMap<>();
            attachment.put("ceshi333.xlsx", input);
            sendEmail("lamyz0417@qq.com", "lamyz0417@qq.com", "lamyz0417@qq.com", "测试邮件", "Dear，您好，谢谢！", attachment);
            
            //附件是 字节源
        	Map<String, DataSource> attachment2 = new HashMap<>();
        	attachment2.put("ceshiceshi.xlsx", new ByteArrayDataSource(input, getMimeType(fileName)));
            sendEmailData("lamyz0417@qq.com", "lamyz0417@qq.com", "lamyz0417@qq.com", "测试邮件2", "Dear，您好，谢谢！", attachment2);
        } catch (Exception e) {
        	logger.error(e.getMessage(), e);
        }
    }
    
    /**
	 * @param receiveAddress 收件人
	 * @param receiveAddressCc 抄送人
	 * @param receiveAddressBcc 密送人
     * @param subject 邮件主题
     * @param text 正文
     * @param attachment 附件<附件名称, 附件源数据>
	 * @throws Exception
	 */
    public static void sendEmailData(String receiveAddress, String receiveAddressCc, String receiveAddressBcc, String subject, String text, Map<String, DataSource> attachment) throws Exception {
		
		EmailSendInfo emailInfo = new EmailSendInfo();
		emailInfo.setSendUserAddress(ParameterConfigUtil.getValue("lam.email.sendUserAddress"));
		emailInfo.setSendUserPassword(ParameterConfigUtil.getValue("lam.email.sendUserPassword"));
		emailInfo.setSmtpHost(ParameterConfigUtil.getValue("lam.email.smtpHost"));
		emailInfo.setSmtpPort(ParameterConfigUtil.getValue("lam.email.smtpPort"));
		emailInfo.setImapHost(ParameterConfigUtil.getValue("lam.email.imapHost"));
		emailInfo.setImapPort(ParameterConfigUtil.getValue("lam.email.imapPost"));
		
		emailInfo.setReceiveAddress(receiveAddress);
		emailInfo.setReceiveAddressCc(receiveAddressCc);
		emailInfo.setReceiveAddressBcc(receiveAddressBcc);
		emailInfo.setSubject(subject);
		emailInfo.setText(text);
		emailInfo.addAttachmentAll(attachment);
		
		EmailUtil.sendEmail(emailInfo);
	}
    
    /**
     * 
     * @param receiveAddress 收件人
     * @param subject 邮件主题
     * @param text 正文
     * @throws Exception
     */
    public static void sendEmail(String receiveAddress, String subject, String text) throws Exception {
		sendEmail(receiveAddress, null, null, subject, text, null);
	}
    
    /**
     * 
     * @param receiveAddress 收件人
     * @param subject 邮件主题
     * @param text 正文
     * @param attachment 附件<附件名称, 附件输入流>
     * @throws Exception
     */
    public static void sendEmail(String receiveAddress, String subject, String text, Map<String, InputStream> attachment) throws Exception {
		sendEmail(receiveAddress, null, null, subject, text, attachment);
	}
    
    /**
	 * @param receiveAddress 收件人
	 * @param receiveAddressCc 抄送人
	 * @param receiveAddressBcc 密送人
     * @param subject 邮件主题
     * @param text 正文
	 * @throws Exception
	 */
    public static void sendEmail(String receiveAddress, String receiveAddressCc, String receiveAddressBcc, String subject, String text) throws Exception {
    	sendEmail(receiveAddress, receiveAddressCc, receiveAddressBcc, subject, text, null);
    }
    
	/**
	 * @param receiveAddress 收件人
	 * @param receiveAddressCc 抄送人
	 * @param receiveAddressBcc 密送人
     * @param subject 邮件主题
     * @param text 正文
     * @param attachment 附件<附件名称, 附件输入流>
	 * @throws Exception
	 */
	public static void sendEmail(String receiveAddress, String receiveAddressCc, String receiveAddressBcc, String subject, String text, Map<String, InputStream> attachment) throws Exception {
		Map<String, DataSource> attachmentMap = new LinkedHashMap<>();
		if(CollectionUtil.isNotEmpty(attachment)) {
			for (Entry<String, InputStream> entry : attachment.entrySet()) {
				attachmentMap.put(entry.getKey(), new ByteArrayDataSource(entry.getValue(), EmailUtil.getMimeType(entry.getKey())));
			}
		}
		sendEmailData(receiveAddress, receiveAddressCc, receiveAddressBcc, subject, text, attachmentMap);
	}
    
    public static String getMimeType(String fileName){
		return EmailSendInfo.getMimeType(fileName);
	}
    
    /**
     *  发送邮件带附件
     * @author 刘波
     * @return void
     */
    public static void sendEmail(EmailSendInfo emailInfo) throws Exception {
    	
        // 得到回话对象
        Session session = Session.getDefaultInstance(emailInfo.getSmtpProperties(), new javax.mail.Authenticator() {
            //Authenticating the password
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(emailInfo.getSendUserAddress(), emailInfo.getSendUserPassword());
            }
        });
        // 获取邮件对象
        Message message = new MimeMessage(session);
        // 设置发件人邮箱地址
        message.setFrom(new InternetAddress(emailInfo.getSendUserAddress()));
        // 设置收件人邮箱地址
        message.setRecipients(Message.RecipientType.TO, getAddress(emailInfo.getReceiveAddress()));
    	// 设置抄送人邮箱地址
        message.setRecipients(Message.RecipientType.CC, getAddress(emailInfo.getReceiveAddressCc()));
    	// 设置抄送人邮箱地址
    	message.setRecipients(Message.RecipientType.BCC, getAddress(emailInfo.getReceiveAddressBcc()));
        // 设置邮件标题
        message.setSubject(emailInfo.getSubject());
        
        // 向multipart对象中添加邮件的各个部分内容，包括文本内容和附件
        Multipart multipart = new MimeMultipart();
        // 添加邮件正文
        BodyPart contentPart = new MimeBodyPart();
        contentPart.setContent(emailInfo.getText(), emailInfo.getTextType());
        multipart.addBodyPart(contentPart);
        // 将multipart对象放到message中
        message.setContent(multipart);
        
        
        // 添加附件的内容
        if (emailInfo.hasAttachment()) {
        	int i=0;
        	for (Entry<String, DataSource> entry : emailInfo.getAttachment().entrySet()) {
        		BodyPart attachmentBodyPart = new MimeBodyPart();
        		//MimeUtility.encodeWord可以避免文件名乱码
        		attachmentBodyPart.setFileName(MimeUtility.encodeWord(StringUtils.isBlank(entry.getKey())?"附件"+i++:entry.getKey()));
                attachmentBodyPart.setDataHandler(new DataHandler(entry.getValue()));
                multipart.addBodyPart(attachmentBodyPart);
			}
        }
        // 保存邮件
        message.saveChanges();
        send(session, message, emailInfo, 0);
        if(emailInfo.isSave2SendEmailFolder()){
        	saveEmailToSendEmailFolder(message, emailInfo);
        }
    }
    
    private static void send(Session session, Message message, EmailSendInfo emailInfo, int errCount) throws Exception{
    	// 得到邮差对象
    	Transport transport = session.getTransport();
        try {
			// QQ邮箱密码为开通的stmp服务后得到的客户端授权码
			transport.connect(emailInfo.getSendUserAddress(), emailInfo.getSendUserPassword());
			// 发送邮件
			transport.sendMessage(message, message.getAllRecipients());
		} catch (Exception e) {
			errCount++;
			if(e instanceof SendFailedException && emailInfo.isInvalidAddressesResend() && errCount < MAX_RETRY_COUNT){
				resendEmail(session, message, emailInfo, ((SendFailedException)e).getInvalidAddresses(), errCount);
			}else{
				logger.error("邮件发送失败， errCount："+errCount, e);
				throw e;
			}
		} finally {
			transport.close();
		}
    }
    
	private static void resendEmail(Session session, Message message, EmailSendInfo emailInfo, Address[] invalidAddresses, int errCount) throws Exception {
		logger.error("发送邮件时检查到无效的邮件地址：{}，将剔除这些收件人后重新发送！", JsonUtil.toJsonString(invalidAddresses));
		List<InternetAddress> toAddress = new ArrayList<>(Arrays.asList(getAddress(emailInfo.getReceiveAddress())));
		List<InternetAddress> ccAddress = new ArrayList<>(Arrays.asList(getAddress(emailInfo.getReceiveAddressCc())));
		List<InternetAddress> bccAddress = new ArrayList<>(Arrays.asList(getAddress(emailInfo.getReceiveAddressBcc())));
		for (Address address : invalidAddresses) {
			toAddress.remove(address);
			ccAddress.remove(address);
			bccAddress.remove(address);
		}
		
		if(toAddress.isEmpty()) {
			logger.error("邮件发送失败，有效收件人为空！");
			throw new BaseException("有效收件人为空！");
		}
		// 设置收件人邮箱地址
        message.setRecipients(Message.RecipientType.TO, toAddress.toArray(new InternetAddress[toAddress.size()]));
        
    	// 设置抄送人邮箱地址
        message.setRecipients(Message.RecipientType.CC, ccAddress.toArray(new InternetAddress[ccAddress.size()]));
    	// 设置密送人邮箱地址
    	message.setRecipients(Message.RecipientType.BCC, bccAddress.toArray(new InternetAddress[bccAddress.size()]));
		send(session, message, emailInfo, errCount);
	}
    
    //获取邮件目的地址
    public static InternetAddress[] getAddress(String emilAddress) throws Exception {
    	if(StringUtils.isBlank(emilAddress)){
    		return new InternetAddress[0];
    	}
    	String[] adss = emilAddress.split(",");
        InternetAddress[] addressArr = new InternetAddress[adss.length];
        for (int i = 0; i < adss.length; i++) {
        	addressArr[i] = new InternetAddress(adss[i]);
        }
        return addressArr;
    }
    
    /**
     *  保存邮件到发件箱
     */
    private static void saveEmailToSendEmailFolder(Message message, EmailSendInfo emailInfo) {
        Store store = null;
        Folder sendFolder = null;
        try {
        	// 创建Session实例对象
            Session session = Session.getInstance(emailInfo.getImapProperties());
            URLName urln = new URLName("imap", emailInfo.getSmtpHost(), emailInfo.getImapPort(), null, 
            		emailInfo.getSendUserAddress(), emailInfo.getSendUserPassword());
            // 创建IMAP协议的Store对象
            store = session.getStore(urln);
            store.connect();
            // 获得发件箱
            sendFolder = store.getFolder("已发送邮件");
            // 以读写模式打开发件箱
            sendFolder.open(Folder.READ_WRITE);
            message.setFlag(Flags.Flag.SEEN, true); //设置已读标志
            sendFolder.appendMessages(new Message[] { message });
            logger.info("已保存到发件箱！");
        } catch (Exception e) {
            logger.error("邮件保存到发件箱失败！", e);
        } finally {
            // 判断发件文件夹是否打开如果打开则将其关闭
            if (sendFolder != null && sendFolder.isOpen()) {
                try {
                    sendFolder.close(true);
                } catch (MessagingException e) {
                	logger.error(e.getMessage(), e);
                }
            }
            // 判断邮箱存储是否打开如果打开则将其关闭
            if (store != null && store.isConnected()) {
                try {
                    store.close();
                } catch (MessagingException e) {
                	logger.error(e.getMessage(), e);
                }
            }
        }
    }
    
}
